	.code16
# rewrite with AT&T syntax by falcon <wuzhangjin@gmail.com> at 081012
#
#	setup.s		(C) 1991 Linus Torvalds
#
# setup.s is responsible for getting the system data from the BIOS,
# and putting them into the appropriate places in system memory.
# both setup.s and system has been loaded by the bootblock.
#
# This code asks the bios for memory/disk/other parameters, and
# puts them in a "safe" place: 0x90000-0x901FF, ie where the
# boot-block used to be. It is then up to the protected mode
# system to read them from there before the area is overwritten
# for buffer-blocks.
#

# NOTE# These had better be the same as in bootsect.s#


# setup.s 是一个操作系统加载程序，它的主要作用是利用 ROM BIOS 中断读取机器系统数据，并将这
# 些数据保存到 0x90000 开始的位置（覆盖掉了 bootsect 程序所在的地方)，所取得的参数和保留的内存位
# 置见下表所示。这些参数将被内核中相关程序使用，例如字符设驱动程序集中的 console.c和 ty.io.c
# 程序等。
/*
| 起始内存地址| 长度（字节）| 名称   | 描述
|---------|---------|-----------|---------------------------------------------------
| 0x90000 |  2      | 光标位置   | 列号(0x00-最左端)，行号(0x00-最顶端)
| 0x90002 |  2      | 扩展内存数 | 系统从1MB开始的扩展内存数值(KB)
| 0x90004 |  2      | 显示页面   | 当前显示页面
| 0x90006 |  1      | 显示模式   |
| 0x90007 |  1      | 字符列数   |
| 0x90008 |  2      | ??       |
| 0x9000A |  1      | 显示内存   | 显示内存(0x00-64k,0x01-128k,0x02-192k,0x03-256k)
| Ox9000B |  1      | 显示状态   | 0x00-彩色,YO=0x3dx； 0x01-单色,1/O=0x3bx
| Ox9000C |  2      | 特征参数   | 显示卡特性参数
| Ox9000E |  1      | 屏幕行数   | 屏幕当前显示行数
| Ox9000F |  1      | 屏幕列数   | 屏幕当前显示列数
| Ox90080 |  16     | 硬盘参数表 | 第1个硬盘的参数表
| 0x90090 |  16     | 硬盘参数表 | 第2个硬盘的参数表（如果没有，则清零）
| Ox901FC |  2      | 根设备号   | 根文件系统所在的设备号 (bootsec.s 中设置）
*/

	.equ INITSEG, 0x9000	# we move boot here - out of the way
	.equ SYSSEG, 0x1000	# system loaded at 0x10000 (65536).
	.equ SETUPSEG, 0x9020	# this is the current segment

	.global _start, begtext, begdata, begbss, endtext, enddata, endbss
	.text
	begtext:
	.data
	begdata:
	.bss
	begbss:
	.text

	ljmp $SETUPSEG, $_start	
_start:
	mov %cs,%ax
	mov %ax,%ds
	mov %ax,%es
	mov	$INITSEG, %ax	# this is done in bootsect already, but...
    mov	%ax, %ds
#
##print some message
#
#	mov $0x03, %ah
#	xor %bh, %bh
#	int $0x10

#	mov $29, %cx
#	mov $0x000b,%bx
#	mov $msg12,%bp
#	mov $0x1301, %ax
#	int $0x10

# Get memory size (extended mem, kB)
# 利用 B10S 中断 0x15 功能号 ah= Ox88 取系统所含扩展内存大小并保存在内存0x90002 处。
# ！返回：ax=从0x100000（1M）处开始的扩展内存大小(KB)。若出错则 CF 置位，ax=出错码。
	mov	$0x88, %ah 
	int	$0x15
	mov	%ax, %ds:2 # it from 0x90002

# check for EGA/VGA and some config parameters
# 检查显示方式（EGA/VGA）并取参数。
# 调用BIOS中断0x10功能号0x12（视频子系统配置）取EBA配置信息。
# ah = 0x12，bl = 0x10 - 取EGA配置信息。
# 返回：
# bh =显示状态(0x00 -彩色模式，I/O端口=0x3dX； 0x01 -单色模式，I/O端口=0x3bX)。存于0x9000B内存处,
# bl = 安装的显示内存(0x00 - 64k；0x01 - 128k；0x02 - 192k；0x03 = 256k。EGA显存, 存于0x9000A内存处)
# cx = 显示卡特性参数(参见程序后对BIOS视频中断0x10的说明)。 分别存于0x9000D和0x9000C内存处,
	mov	$0x12, %ah
	mov	$0x10, %bl
	int	$0x10
	mov	%ax, %ds:8
	mov	%bx, %ds:10
	mov	%cx, %ds:12
	mov	$0x5019, %ax  # 在ax中预置屏幕默认行列值（ah = 80列；al=25行）。
#	cmp	$0x10, %bl    # 若中断返回bl值为0x10，则表示不是VGA显示卡，跳转。
#	je	novga
#	call	chsvga
novga:
    mov	%ax, %ds:14  # 保存屏幕当前行列值（0x9000E，0x9000F）。

    # 使用BIOS中断0x10功能0x03取屏幕当前光标位置，并保存在内存0x90000处（2字节）。
    # 控制台初始化程序console.c会到此处读取该值。
    # BIOS中断0x10功能号 ah = 0x03，读光标位置。
    # 输入：bh = 页号
    # 返回：ch = 扫描开始线；cl = 扫描结束线；dh = 行号(0x00顶端)；dl = 列号(0x00最左边)。
	mov	$0x03, %ah	# read cursor pos
	xor	%bh, %bh
	int	$0x10		# save it in known place, con_init fetches
	mov	%dx, %ds:0	# it from 0x90000.

# Get video-card data:
	mov	$0x0f, %ah
	int	$0x10
	mov	%bx, %ds:4	# bh = display page
	mov	%ax, %ds:6	# al = video mode, ah = window width

# Get hd0 data
# 获取第1个硬盘的参数信息, 1个硬盘参数信息共16字节(0x10）。
# 第1个硬盘参数表的首地址竟然是中断向量 0x41 的向量值！
# 第2个硬盘参数表紧接在第1个表的后面，中断向量 0x46 的向量值也指向第2个硬盘的参数表首址。
# lds %ds:4*0x41, %si， 将4*0x41 (=0x104)地址处的四字节读出，分别赋给 段地址ds(高2字节)和 偏移值si(低2字节)。
# BIOS在0:4 * 0x41(中断号0x41对应内存)处存了存储第1个硬盘参数信息的内存地址。
# 以下是实际的调试数据
# (gdb) x /20xh 0x104
# 0x104 <ok3_read+20>:    0x003d  0x9fc0  0xff53  0xf000  0x72a0  0xc000  0xff53  0xf000
# 0x114 <read_track+15>:  0xff53  0xf000  0xff53  0xf000  0xff53  0xf000  0xff53  0xf000
# 0x124 <read_track+31>:  0xff53  0xf000  0xff53  0xf000
# (gdb) x /20xh 0x9fc3d
# 0x9fc3d:        0x0079  0x0010  0xff00  0x00ff  0x00c8  0x0000  0x0079  0x003f
# 0x9fc4d:        0x0000  0x0000  0x0000  0x0000  0x0000  0x0000  0x0000  0x0000
# 0x9fc5d:        0x0000  0x0000  0x0000  0x0000

	mov	$0x0000, %ax
	mov	%ax, %ds
	lds	%ds:4*0x41, %si  #取中断向量 0x41 的值，也即 hd0 参数表的地址＞ds:si
	mov	$INITSEG, %ax
	mov	%ax, %es
	mov	$0x0080, %di
	mov	$0x10, %cx
	rep
	movsb  #将ds:si指向的16字节内容拷贝到 始于es:di的内存段中，即[0x90080, 0x90090)中。

# Get hd1 data
# 获取第2个硬盘的参数信息存于[0x90090, 0x900a0)内存段中,
# BIOS将硬盘信息存于其地址之后的16字节内存中。
	mov	$0x0000, %ax
	mov	%ax, %ds
	lds	%ds:4*0x46, %si
	mov	$INITSEG, %ax
	mov	%ax, %es
	mov	$0x0090, %di
	mov	$0x10, %cx
	rep
	movsb

## modify ds
	mov $INITSEG,%ax
	mov %ax,%ds
	mov $SETUPSEG,%ax
	mov %ax,%es

##show cursor pos:
	mov $0x03, %ah 
	xor %bh,%bh
	int $0x10
	mov $11,%cx
	mov $0x000c,%bx
	mov $cur,%bp
	mov $0x1301,%ax
	int $0x10
##show detail
	mov %ds:0 ,%ax
	call print_hex
	call print_nl

##show memory size
	mov $0x03, %ah
	xor %bh, %bh
	int $0x10
	mov $12, %cx
	mov $0x000a, %bx
	mov $mem, %bp
	mov $0x1301, %ax
	int $0x10

##show detail
	mov %ds:2 , %ax
	call print_hex

##show 
	mov $0x03, %ah
	xor %bh, %bh
	int $0x10
	mov $25, %cx
	mov $0x000d, %bx
	mov $cyl, %bp
	mov $0x1301, %ax
	int $0x10
##show detail
	mov %ds:0x80, %ax
	call print_hex
	call print_nl

##show 
	mov $0x03, %ah
	xor %bh, %bh
	int $0x10
	mov $8, %cx
	mov $0x000e, %bx
	mov $head, %bp
	mov $0x1301, %ax
	int $0x10
##show detail
	mov %ds:0x82, %ax
	call print_hex
	call print_nl

##show 
	mov $0x03, %ah
	xor %bh, %bh
	int $0x10
	mov $8, %cx
	mov $0x000f, %bx
	mov $sect, %bp
	mov $0x1301, %ax
	int $0x10
##show detail
	mov %ds:0x8e, %ax
	call print_hex
	call print_nl
#l:
#	jmp l
##
# Check that there IS a hd1 :-)
    # 检查系统是否有第2个硬盘。如果没有则把第2个表清零。
    # 利用 BIOS 中断调用 0x13 的取盘类型功能，功能号 ah = 0x15:
    # 输入：dl= 驱动器号（0x8X 是硬盘：0x80 指第1个硬盘，0x81 第2个硬盘）
    # 输出：ah= 类型码：
    #      00 - 没有这个盘，CF 置位；
    #      01- 是软驱，没有 change-line 支持；
    #      02- 是软驱(或其他可移动设备)，有 change-line 支持；
    #      03- 是硬盘。
	mov	$0x01500, %ax
	mov	$0x81, %dl
	int	$0x13
	jc	no_disk1
	cmp	$3, %ah
	je	is_disk1
no_disk1:
	mov	$INITSEG, %ax
	mov	%ax, %es
	mov	$0x0090, %di
	mov	$0x10, %cx
	mov	$0x00, %ax
	rep
	#stosb, stosw, stosd。这三个指令把al/ ax/ eax的内容存储到es:di指向的内存单元中，同时xdi的值根据方向标志的值增加或者减少。
	# 此处的业务含义就是 把第2个硬盘表清零
	stosb
is_disk1:

# 关于linux 0.11 CPU参考手册。
# ---------------------------
# linux 0.11约在1991年12月改版诞生。
# Intel 80486于1989年改版诞生, 1993年改版诞生Intel Pentium。
# 所以linux 0.11 CPU参考手册应尽可能参考80486,其次是80386, 80286。
# 不过没有搜到80486参考手册, 就参考80386吧。
# 也可以参考Pentium及以上版本文档中的一些说辞。

# now we want to move to protected mode ...
# 为支持80x86CPU在保护模式下运行, 先做好以下准备。!
#
# BIOS初始化完成后, I/O硬件处于已启动状态,
# 为防止在设置IDT和初始化PIC后有中断放生,
# 在操作系统初始化完成之前, 先禁止所有中断。
# 直到在main函数中做了足够初始化后, 才重新允许中断得哦。
	cli			# no interrupts allowed ! 

# first we move the system to it's rightful place
# 以段为单位(64Kb)将[0x10000, 0x90000) 内存段中的512Kb内容拷贝至[0x0, 0x80000)内存段。
# 虽然当前系统没有完全占用512k，但是预留的空间是这么大，后期系统变大，这里也不用改动
#
# 移动前，操作系统程序存于 [0x10000, 0x40000)内存段中，共192kB。
#
# 在bootsect.s中有提到, [0x0, 0x400)内存段存储了BIOS中断向量表等信息,
# 由于linux 0.11在进入保护模式后不会再使用BIOS中断调用,
# 所以将该内存段可用作存储操作系统程序了。
# 除了能节约内存外, 还有个好处是 从0x0开始存储操作系统，段基址和偏移地址相等。
	mov	$0x0000, %ax
	cld			# 'direction'=0, movs moves forward
do_move:
	mov	%ax, %es	# destination segment
	add	$0x1000, %ax
	cmp	$0x9000, %ax
	jz	end_move
	mov	%ax, %ds	# source segment
	sub	%di, %di
	sub	%si, %si
	mov $0x8000, %cx
	rep
	movsw
	jmp	do_move

# then we load the segment descriptors

end_move:
	mov	$SETUPSEG, %ax	# right, forgot this at first. didn't work :-)
	mov	%ax, %ds

# 为保护模式准备中断描述符表(Interrupt Descriptor Table)和
# 全局描述符表(Global Descriptor Table)。
#
# 将IDT和GDT的基址和长度分别加载给IDTR和GDTR寄存器。
# GDTR/IDTR寄存器位格式。
# |47                   16|15                         0|
# ------------------------------------------------------
# | descriptor table addr | length of descriptor table |
# ------------------------------------------------------
# bit[15..0]: 存储IDT或GDT长度(字节);
# bit[47..16]: 存储IDT或GDT的内存地址。
#
	lidt	idt_48		# load idt with 0,0
	lgdt	gdt_48		# load gdt with whatever appropriate

# that was painless, now we enable A20

	#call	empty_8042	# 8042 is the keyboard controller
	#mov	$0xD1, %al	# command write
	#out	%al, $0x64
	#call	empty_8042
	#mov	$0xDF, %al	# A20 on
	#out	%al, $0x60
	#call	empty_8042
	#为访问扩展内存, 开启第21根地址线 # open A20 line(Fast Gate A20).
	inb     $0x92, %al        # 读出原数据
	orb     $0b00000010, %al  # 将第`2`位置为`1`
	outb    %al, $0x92        # 写入数据

# well, that went ok, I hope. Now we have to reprogram the interrupts :-(
# we put them right after the intel-reserved hardware interrupts, at
# int 0x20-0x2F. There they won't mess up anything. Sadly IBM really
# messed this up with the original PC, and they haven't been able to
# rectify it afterwards. Thus the bios puts interrupts at 0x08-0x0f,
# which is used for the internal hardware interrupts as well. We just
# have to reprogram the 8259's, and it isn't fun.
#    PC 机使用 2 个 8259A 芯片，关于对可编程控制器 8259A 芯片的编程方法请参见本程序后的介绍。
#	 第 336 行上定义的两个字(0x00eb)是直接使用机器码表示的两条相对跳转指令，起延时作用。
#	 0xeb 是直接近跳转指令的操作码，带 1 个字节的相对位移值。因为此跳转范围是 -127 到 127.
#	 CPU通过把这个相对位移值加到 EIP 寄存器中就形成一个新有效地址。此时 EIP 指向下一条被执行
#	 的指令。执行时所花费的 CPU 时钟周期数是 7 至 10个。 0x00eb 表示跳转值是 0 的一条指令，
#	 因此还是直接执行下一条指令。这两条指令共可提供 14 - 20 个CPU时钟周期的延迟时间。在 as86
#	 中没有表示相应指令的助记符，因此 Linus 在 setup.s 等一些汇编程序中就直接使用机器码来表
#	 示这种指令。另外，每个空操作指令 NOP 的时钟周期数是 3 个，因此若要达到相同的延迟效果就
#	 需要 6 至 7 个NOP指令。

#	【设置 ICW1】
#    在初始化8259A之前, 写端口表明设置初始化命令(ICW)寄存器组。
#    0x20和0xA0分别为8259A-1和8259A-2的端口地址,
#    端口地址最低位为0且0x11的bit[4]=1则表示:
#    0x11为第一条初始化命令, 设置ICW1,
#    bit[0]=1表明需初始化命令ICW4,
#    bit[1](SNGL)=0表示8259A为多片,
#    bit[3](LTIM)=0表示边沿触发中断。
	mov	$0x11, %al		# initialization sequence(ICW1)
					# ICW4 needed(1),CASCADE mode,Level-triggered
	out	%al, $0x20		# send it to 8259A-1
	.word	0x00eb,0x00eb		# jmp $+2, jmp $+2
	out	%al, $0xA0		# and to 8259A-2
	.word	0x00eb,0x00eb
#	【设置 ICW2】
#    0x21和0xA1分别为8259A-1和8259A-2的端口地址,
#    端口地址最低位为1, 表明下发第2条初始化命令，设置ICW2=0x20/0x28,
#    即8259A-1/2的起始中断号分别为0x20和0x28。
	mov	$0x20, %al		# start of hardware int's (0x20)(ICW2)
	out	%al, $0x21		# from 0x20-0x27
	.word	0x00eb,0x00eb
	mov	$0x28, %al		# start of hardware int's 2 (0x28)
	out	%al, $0xA1		# from 0x28-0x2F
	.word	0x00eb,0x00eb		#               IR 7654 3210

#	【设置 ICW3】
#    在下发ICW2初始化命令后且端口地址最低位为1,
#    则表示下发ICW3初始化命令(ICW1的SNGL=0表示有多片中断控制器级联)
#    ICW3=0x04表明主中断控制芯片8259A-1的IR2上连了一个从片(8259A-2),
#    ICW3=0x02表明从中断控制芯片8259A-2的连接到主片(8259A-1)的IR2上。
	mov	$0x04, %al		# 8259-1 is master(0000 0100) --\
	out	%al, $0x21		#				|
	.word	0x00eb,0x00eb		#			 INT	/
	mov	$0x02, %al		# 8259-2 is slave(       010 --> 2)
	out	%al, $0xA1
	.word	0x00eb,0x00eb

#	【设置 ICW4】
#   ICW1 bit[0]=1且端口地址最低位为1且下发数据高3位为0表明下发初始化命令ICW4,
#   bit[0]=1表示当前CPU为8086;
#   bit[1](AEOI)=0表示8259A-1/2不自动结束中断,即需中断程序给8259A发结束命令EOI才能清当前中断

	mov	$0x01, %al		# 8086 mode for both
	out	%al, $0x21
	.word	0x00eb,0x00eb
	out	%al, $0xA1
	.word	0x00eb,0x00eb

#   按照初始化流程设置ICW初始化8259A后,
#   再写端口表明操作OCW1-OCW3寄存器组。
#   端口地址最低位为1, 表示下发OCW1操作命令,
#   即操作中断屏蔽寄存器IMR,
#   IMR bit[n]置1表示屏蔽引脚n上的中断请求, 置0表允许。
	mov	$0xFF, %al		# mask off all interrupts for now
	out	%al, $0x21
	.word	0x00eb,0x00eb
	out	%al, $0xA1

# well, that certainly wasn't fun :-(. Hopefully it works, and we don't
# need no steenking BIOS anyway (except for the initial loading :-).
# The BIOS-routine wants lots of unnecessary data, and it's less
# "interesting" anyway. This is how REAL programmers do it.
#
# Well, now's the time to actually move into protected mode. To make
# things as simple as possible, we do no register set-up or anything,
# we let the gnu-compiled 32-bit programs do that. We just jump to
# absolute address 0x00000, in 32-bit protected mode.
    # 下面设置并进入32 位保护模式运行。
    # 此时段寄存器仍然指向与实地址模式中相同的线性地址处（在实地址模式下线性地址与物理内存地址相同）。
    # 在设置该比特位后，随后一条指令必须是一条段间跳转指令 用于刷新 CPU 当前指令队列。
    # 因为 CPU 是在执行一条指令之前就己从内存读取该指令并对其进行 解码。然而在进入保护模式以后那些属于实模式的预先取得的指令信息就变得不再有效。
    # 而一条 段间跳转指令就会刷新 CPU 的当前指令队列，即丢弃这些无效信息。
    # 另外，在 Intel 公司的手册上建议80386 或以上 CPU 应该使用指令“mov cr0,ax”切换到保护模式。lmsw 指令仅用于兼容以 前的 286 CPU。
	#mov	$0x0001, %ax	# protected mode (PE) bit
	#lmsw	%ax		# This is it!
	#The CR0 register is a 32-bit register that holds various control flags for the CPU, including the protected mode enable (PE) bit.
	mov	%cr0, %eax	# get machine status(cr0|MSW)	
	bts	$0, %eax	# turn on the PE-bit 
	mov	%eax, %cr0	# protection enabled

	# 我们已经将 system模块移动到 Ox00000 开始的地方，所以偏移地址是0。
	# 而段值8已经 是保护模式下的段选择符了，用于选择描述符表和描述符表项以及所要求的特权级。
    # 此处的段选择符8(Ob0000, 0000,0000,1000）表示请求特权级0、使用全局描述符表 GDT 中第2个段描述符项
    # 该项指出代码的基地址是0（参见 450 行)，因此这里的跳转指令就会去执行 system 中的代码。
    #
	# segment-descriptor        (INDEX:TI:RPL)
	.equ	sel_cs0, 0x0008 # select for code segment 0 (  001:0 :00) 
	ljmp	$sel_cs0, $0	# jmp offset 0 of code segment 0 in gdt

# This routine checks that the keyboard command queue is empty
# No timeout is used - if this hangs there is something wrong with
# the machine, and we probably couldn't proceed anyway.
empty_8042:
	.word	0x00eb,0x00eb
	in	$0x64, %al	# 8042 status port
	test	$2, %al		# is input buffer full?
	jnz	empty_8042	# yes - loop
	ret

# Routine trying to recognize type of SVGA-board present (if any)
# and if it recognize one gives the choices of resolution it offers.
# If one is found the resolution chosen is given by %al,%ah (rows,cols).

chsvga:
    cld
    pushl   %ds
    pushl   %cs
    popl    %ds
    movw    $0xc000, %ax
    movw    %ax, %es
    leal    msg1, %esi
    call    prtstr
nokey:
    inb     $0x60, %al
    cmpb    $0x82, %al
    jb      nokey
    cmpb    $0xe0, %al
    ja      nokey
    cmpb    $0x9c, %al
    je      svga
    movw    $0x5019, %ax
    popl    %ds
    ret
svga:
    leal    idati, %esi        # Check ATI 'clues'
    movw    $0x31, %di
    movw    $0x09, %cx
    repe    cmpsb
    jne     noati
    leal    dscati, %esi
    leal    moati, %edi
    leal    selmod, %ecx
    jmp     *%ecx
noati:
    movw    $0x200f, %ax       # Check Ahead 'clues'
    movw    $0x3ce, %dx
    outw    %ax, %dx
    incw    %dx
    inb     %dx, %al
    cmpb    $0x20, %al
    je      isahed
    cmpb    $0x21, %al
    jne     noahed
isahed:
    leal    dscahead, %esi
    leal    moahead, %edi
    leal    selmod, %ecx
    jmp     *%ecx
noahed:
    movw    $0x3c3, %dx        # Check Chips & Tech. 'clues'
    inb     %dx, %al
    orb     $0x10, %al
    outb    %al, %dx
    movw    $0x104, %dx
    inb     %dx, %al
    movb    %al, %bl
    movw    $0x3c3, %dx
    inb     %dx, %al
    andb    $0xef, %al
    outb    %al, %dx
    cmpb    idcandt, %bl
    jne     nocant
    leal    dsccandt, %esi
    leal    mocandt, %edi
    leal    selmod, %ecx
    jmp     *%ecx
nocant:
    movw    $0x3d4, %dx        # Check Cirrus 'clues'
    movb    $0x0c, %al
    outb    %al, %dx
    incw    %dx
    inb     %dx, %al
    movb    %al, %bl
    xorb    %al, %al
    outb    %al, %dx
    decw    %dx
    movb    $0x1f, %al
    outb    %al, %dx
    incw    %dx
    inb     %dx, %al
    movb    %al, %bh
    xorb    %ah, %ah
    shlb    $4, %al
    movw    %ax, %cx
    movb    %bh, %al
    shrb    $4, %al
    addw    %ax, %cx
    shlw    $8, %cx
    addw    $6, %cx
    movw    %cx, %ax
    movw    $0x3c4, %dx
    outw    %ax, %dx
    incw    %dx
    inb     %dx, %al
    andb    %al, %al
    jnz     nocirr
    movb    %bh, %al
    outb    %al, %dx
    inb     %dx, %al
    cmpb    $0x01, %al
    jne     nocirr
    call    rst3d4
    leal    dsccirrus, %esi
    leal    mocirrus, %edi
    leal    selmod, %ecx
    jmp     *%ecx
rst3d4:
    movw    $0x3d4, %dx
    movb    %bl, %al
    xorb    %ah, %ah
    shlw    $8, %ax
    addw    $0x0c, %ax
    outw    %ax, %dx
    ret
nocirr:
    call    rst3d4              # Check Everex 'clues'
    movw    $0x7000, %ax
    xorw    %bx, %bx
    int     $0x10
    cmpb    $0x70, %al
    jne     noevrx
    shrw    $4, %dx
    cmpw    $0x678, %dx
    je      istrid
    cmpw    $0x236, %dx
    je      istrid
    leal    dsceverex, %esi
    leal    moeverex, %edi
    leal    selmod, %ecx
    jmp     *%ecx
istrid:
    leal    ev2tri, %ecx
    jmp     *%ecx
noevrx:
    leal    idgenoa, %esi       # Check Genoa 'clues'
    xorw    %ax, %ax
    movb    %es:0x37, %al
    movw    %ax, %di
    movw    $0x04, %cx
    decl    %esi
    decl    %edi
l1:
    incl    %esi
    incl    %edi
    movb    (%esi), %al
    andb    %es:(%edi), %al
    cmpb    (%esi), %al
    loope   l1
    cmpw    $0x00, %cx
    jne     nogen
    leal    dscgenoa, %esi
    leal    mogenoa, %edi
    leal    selmod, %ecx
    jmp     *%ecx
nogen:
    leal    idparadise, %esi    # Check Paradise 'clues'
    movw    $0x7d, %di
    movw    $0x04, %cx
    repe    cmpsb
    jne     nopara
    leal    dscparadise, %esi
    leal    moparadise, %edi
    leal    selmod, %ecx
    jmp     *%ecx
nopara:
    movw    $0x3c4, %dx         # Check Trident 'clues'
    movb    $0x0e, %al
    outb    %al, %dx
    incw    %dx
    inb     %dx, %al
    xchgb   %ah, %al
    movb    $0x00, %al
    outb    %al, %dx
    inb     %dx, %al
    xchgb   %al, %ah
    movb    %al, %bl            # Strange thing ... in the book this wasn't
    andb    $0x02, %bl          # necessary but it worked on my card which
    jz      setb2               # is a trident. Without it the screen goes
    andb    $0xfd, %al          # blurred ...
    jmp     clrb2               #
setb2:
    orb     $0x02, %al          #
clrb2:
    outb    %al, %dx
    andb    $0x0f, %ah
    cmpb    $0x02, %ah
    jne     notrid
ev2tri:
    leal    dsctrident, %esi
    leal    motrident, %edi
    leal    selmod, %ecx
    jmp     *%ecx
notrid:
    movw    $0x3cd, %dx         # Check Tseng 'clues'
    inb     %dx, %al            # Could things be this simple ! :-)
    movb    %al, %bl
    movb    $0x55, %al
    outb    %al, %dx
    inb     %dx, %al
    movb    %al, %ah
    movb    %bl, %al
    outb    %al, %dx
    cmpb    $0x55, %ah
    jne     notsen
    leal    dsctseng, %esi
    leal    motseng, %edi
    leal    selmod, %ecx
    jmp     *%ecx
notsen:
    movw    $0x3cc, %dx         # Check Video7 'clues'
    inb     %dx, %al
    movw    $0x3b4, %dx
    andb    $0x01, %al
    jz      even7
    movw    $0x3d4, %dx
even7:
    movb    $0x0c, %al
    outb    %al, %dx
    incw    %dx
    inb     %dx, %al
    movb    %al, %bl
    movb    $0x55, %al
    outb    %al, %dx
    inb     %dx, %al
    decw    %dx
    movb    $0x1f, %al
    outb    %al, %dx
    incw    %dx
    inb     %dx, %al
    movb    %al, %bh
    decw    %dx
    movb    $0x0c, %al
    outb    %al, %dx
    incw    %dx
    movb    %bl, %al
    outb    %al, %dx
    movb    $0x55, %al
    xorb    $0xea, %al
    cmpb    %bh, %al
    jne     novid7
    leal    dscvideo7, %esi
    leal    movideo7, %edi
selmod:
    pushl   %esi
    leal    msg2, %esi
    call    prtstr
    xorw    %cx, %cx
    movb    (%edi), %cl
    popl    %esi
    pushl   %esi
    push   %cx
tbl:
    pop    %bx
    push   %bx
    movb    %bl, %al
    subb    %cl, %al
    call    dprnt
    call    spcing
    lodsw
    xchgb   %al, %ah
    call    dprnt
    xchgb   %ah, %al
    push   %ax
    movb    $0x78, %al
    call    prnt1
    pop    %ax
    call    dprnt
    call    docr
    loop    tbl
    pop    %cx
    call    docr
    leal    msg3, %esi
    call    prtstr
    popl    %esi
    addb    $0x80, %cl
nonum:
    inb     $0x60, %al          # Quick and dirty...
    cmpb    $0x82, %al
    jb      nonum
    cmpb    $0x8b, %al
    je      zero
    cmpb    %cl, %al
    ja      nonum
    jmp     nozero
zero:
    subb    $0x0a, %al
nozero:
    subb    $0x80, %al
    decb    %al
    xorb    %ah, %ah
    addw    %ax, %di
    incw    %di
    push   %ax
    movb    (%edi), %al
    int     $0x10
    pop    %ax
    shlw    $1, %ax
    addw    %ax, %si
    lodsw
    popl    %ds
    ret
novid7:
    popl    %ds                 # Here could be code to support standard 80x50,80x30
    movw    $0x5019, %ax
    ret

# Routine that 'tabs' to next col.

spcing:
    movb    $0x2e, %al
    call    prnt1
    movb    $0x20, %al
    call    prnt1
    movb    $0x20, %al
    call    prnt1
    movb    $0x20, %al
    call    prnt1
    movb    $0x20, %al
    call    prnt1
    ret

# Routine to print asciiz-string at DS:SI

prtstr:
    lodsb
    andb    %al, %al
    jz      fin
    call    prnt1
    jmp     prtstr
fin:
    ret

# Routine to print a decimal value on screen, the value to be
# printed is put in al (i.e 0-255).

dprnt:
    push   %ax
    push   %cx
    movb    $0x00, %ah
    movb    $0x0a, %cl
    idivb   %cl
    cmpb    $0x09, %al
    jbe     lt100
    call    dprnt
    jmp     skip10
lt100:
    addb    $0x30, %al
    call    prnt1
skip10:
    movb    %ah, %al
    addb    $0x30, %al
    call    prnt1
    pop    %cx
    pop    %ax
    ret

# Part of above routine, this one just prints ascii al

prnt1:
    push   %ax
    push   %cx
    movb    $0x00, %bh
    movw    $0x01, %cx
    movb    $0x0e, %ah
    int     $0x10
    pop    %cx
    pop    %ax
    ret

# Prints <CR> + <LF>

docr:
    push   %ax
    push   %cx
    movb    $0x00, %bh
    movb    $0x0e, %ah
    movb    $0x0a, %al
    movw    $0x01, %cx
    int     $0x10
    movb    $0x0d, %al
    int     $0x10
    pop    %cx
    pop    %ax
    ret



gdt:
    #GDT[0]为保留项, 需用0填充。每一个0是16bit， 共16*4=64bit
	.word	0,0,0,0		# dummy


    # GDT[1] 在GDT表中，该 段描述符的偏移量是0x08，他是内核代码段选择符的值
	.word	0x07FF		# limit=2047，大小是2048
	.word	0x0000		# base address=0 表示基地址 Base 15...0
	.word	0x9A00		# code read/exec  0x9A00 = b1001 1010 0000 0000
	                    # 二进制数据，从左往右分析：
	                    # P=1  内存段存在;
	                    # DPL=00, 内存段特权级最高。
	                    # S=1 && TYPE[3]= 1 表示可执行段描述符;
	                    # C=0  非一致性可执行代码段, 更低特权级代码段不能访问本描述符所描述的内存段;
                        # R=1  内存段可读可执行,
                        # A=0  未访问
                        # 0000 0000 表示基地址 Base 23...16
	.word	0x00C0		# granularity=4096, 0x00C0 = b1100 0000
	                    # G=1 段限长的单位是4KB，所以该段的总限长是2048*4096=8Mb
                        # D=1 对应32位代码和数据段，该标志总设置为1

     # GDT[2] 在GDT表中，该段描述符的偏移量是0x10，他是内核数据段选择符的值
	.word	0x07FF		# limit=2047 (2048*4096=8Mb)
	.word	0x0000		# base address=0
	.word	0x9200		# data read/write 0x9200 = b1001 0010 0000 0000
	                    # 与上一个相比主要是TYPE标志不同，S=1 && TYPE[3]= 0 表示数据段段描述符;
	                    # E=0  扩展方向
                        # W=1  可写
                        # A=0  未访问
	.word	0x00C0		# 同上

# 下面是加载中断描述符表寄存器 idtr 的指令 lidt 要求的 6 字节操作数。前 2 字节是
# IDT 表的限长，后 4 字节是 idt 表在线性地址空间中的 32 位基地址。CPU 要求在进行保护
# 模式之前需设置 IDT 表，因此这里先设置一个长度为 0 的空表。
idt_48:
	.word	0			# idt limit=0
	.word	0,0			# idt base=0L

# 下面是加载中断描述符表寄存器 gdtr 的指令 lgdt 要求的 6 字节操作数。
# 前 2 字节是GDT 表的限长，后 4 字节是 gdt 表在线性地址空间中的 32 位基地址。
# gdt表长度设置 为 2KB (0x800)，因为每 8 字节组成一个段描述符项，所以表中共可用 256 项。
# 当前setup被加载到内存0x90200的位置，所以gdt表的位置就是 0x90200 + gdt

gdt_48:
	.word	0x800			# gdt limit=2048, 256 GDT entries
	.word   512+gdt, 0x9	# gdt base = 0X9xxxx,  0x200 = 0512
	# 512+gdt is the real gdt after setup is moved to 0x9020 * 0x10
print_hex:
	mov $4,%cx
	mov %ax,%dx

print_digit:
	rol $4,%dx	#循环以使低4位用上，高4位移至低4位
	mov $0xe0f,%ax #ah ＝ 请求的功能值，al = 半个字节的掩码
	and %dl,%al
	add $0x30,%al
	cmp $0x3a,%al
	jl outp
	add $0x07,%al

outp:
	int $0x10
	loop print_digit
	ret
#打印回车换行
print_nl:
	mov $0xe0d,%ax
	int $0x10
	mov $0xa,%al
	int $0x10
	ret

msg12:
	.byte 13,10
	.ascii "Now we are in setup ..."
	.byte 13,10,13,10
cur:
	.ascii "Cursor POS:"
mem:
	.ascii "Memory SIZE:"
cyl:
	.ascii "KB"
	.byte 13,10,13,10
	.ascii "HD Info"
	.byte 13,10
	.ascii "Cylinders:"
head:
	.ascii "Headers:"
sect:
	.ascii "Secotrs:"


msg1:	.ascii	"Press <RETURN> to see SVGA-modes available or any other key to continue."
		.byte	0x0d, 0x0a, 0x0a, 0x00
msg2:	.ascii	"Mode:  COLSxROWS:"
		.byte	0x0d, 0x0a, 0x0a, 0x00
msg3:	.ascii	"Choose mode by pressing the corresponding number."
		.byte	0x0d, 0x0a, 0x00

idati:		.ascii	"761295520"
idcandt:	.byte	0xa5
idgenoa:	.byte	0x77, 0x00, 0x66, 0x99
idparadise:	.ascii	"VGA="

# Manufacturer:	  Numofmodes:	Mode:

moati:		.byte	0x02,	0x23, 0x33
moahead:	.byte	0x05,	0x22, 0x23, 0x24, 0x2f, 0x34
mocandt:	.byte	0x02,	0x60, 0x61
mocirrus:	.byte	0x04,	0x1f, 0x20, 0x22, 0x31
moeverex:	.byte	0x0a,	0x03, 0x04, 0x07, 0x08, 0x0a, 0x0b, 0x16, 0x18, 0x21, 0x40
mogenoa:	.byte	0x0a,	0x58, 0x5a, 0x60, 0x61, 0x62, 0x63, 0x64, 0x72, 0x74, 0x78
moparadise:	.byte	0x02,	0x55, 0x54
motrident:	.byte	0x07,	0x50, 0x51, 0x52, 0x57, 0x58, 0x59, 0x5a
motseng:	.byte	0x05,	0x26, 0x2a, 0x23, 0x24, 0x22
movideo7:	.byte	0x06,	0x40, 0x43, 0x44, 0x41, 0x42, 0x45

#msb = Cols lsb = Rows:

dscati:		.word	0x8419, 0x842c
dscahead:	.word	0x842c, 0x8419, 0x841c, 0xa032, 0x5042
dsccandt:	.word	0x8419, 0x8432
dsccirrus:	.word	0x8419, 0x842c, 0x841e, 0x6425
dsceverex:	.word	0x5022, 0x503c, 0x642b, 0x644b, 0x8419, 0x842c, 0x501e, 0x641b, 0xa040, 0x841e
dscgenoa:	.word	0x5020, 0x642a, 0x8419, 0x841d, 0x8420, 0x842c, 0x843c, 0x503c, 0x5042, 0x644b
dscparadise:	.word	0x8419, 0x842b
dsctrident:	.word 	0x501e, 0x502b, 0x503c, 0x8419, 0x841e, 0x842b, 0x843c
dsctseng:	.word	0x503c, 0x6428, 0x8419, 0x841c, 0x842c
dscvideo7:	.word	0x502b, 0x503c, 0x643c, 0x8419, 0x842c, 0x841c


.text
endtext:
.data
enddata:
.bss
endbss:
